Hibernate 映射数据库中Json字段的解决方案

人点击

最近的项目ORM框架使用的是Hibernate,数据库使用PostgreSQL,俩个都是开源的高大上产品,Hibernate就不用说啦,主流的ORM框架,PostgreSQL第一次接触,其中包含Json数据类型的字段,这种类型意味着它可以像非关系型数据库那样存储数据,数据扩展性非常好,这也是项目使用它的主要原因之一。 在Hibernate和PostgreSQL的结合过程中遇到了针对Json数据类型,Hibernate并没有Json这种数据映射字段,想了下是不是版本的问题,也许高版本Hibernate已经支持这种类型啦,下载最新版本Hibernate看下啦,仍然不支持,这点不是很明白,PostgreSQL已经支持Json这么长时间啦,而且现在MySQL最新版本也已经支持Json数据格式啦,身为主流ORM框架怎么到现在还没有提供这种数据类型,不管啦,也许人家提供了其他想法,大公司的想法是我们这些吊私无法猜测的,作为我们就是寻找解决方案就可以啦,毕竟人家已经开源了嘛。
毕竟这俩框架已经诞生这么长时间啦,这种常见问题肯定有了很好的解决方案,果然在stackoverflow找到了一些解决方案,这里只做整理说明,能够解决当前项目中的问题,不做深入探究。

现在有俩种方案:

1、在数据库端处理

1
2
3
CREATE OR REPLACE FUNCTION json_intext(text) RETURNS json AS $$
SELECT json_in($1::cstring);
$$ LANGUAGE SQL IMMUTABLE;

上面的函数是将指定text类型数据转换成json数据个数

2、在Hibernate端处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
import org.hibernate.HibernateException;
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.usertype.UserType;

import java.io.Serializable;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;

/**
* @author timfulmer
*/
public class StringJsonUserType implements UserType {

/**
* Return the SQL type codes for the columns mapped by this type. The
* codes are defined on <tt>java.sql.Types</tt>.
*
* @return int[] the typecodes
* @see java.sql.Types
*/
@Override
public int[] sqlTypes() {
return new int[] { Types.JAVA_OBJECT};
}

/**
* The class returned by <tt>nullSafeGet()</tt>.
*
* @return Class
*/
@Override
public Class returnedClass() {
return String.class;
}

/**
* Compare two instances of the class mapped by this type for persistence "equality".
* Equality of the persistent state.
*
* @param x
* @param y
* @return boolean
*/
@Override
public boolean equals(Object x, Object y) throws HibernateException {

if( x== null){

return y== null;
}

return x.equals( y);
}

/**
* Get a hashcode for the instance, consistent with persistence "equality"
*/
@Override
public int hashCode(Object x) throws HibernateException {

return x.hashCode();
}

/**
* Retrieve an instance of the mapped class from a JDBC resultset. Implementors
* should handle possibility of null values.
*
* @param rs a JDBC result set
* @param names the column names
* @param session
* @param owner the containing entity @return Object
* @throws org.hibernate.HibernateException
*
* @throws java.sql.SQLException
*/
@Override
public Object nullSafeGet(ResultSet rs, String[] names, SessionImplementor session, Object owner) throws HibernateException, SQLException {
if(rs.getString(names[0]) == null){
return null;
}
return rs.getString(names[0]);
}

/**
* Write an instance of the mapped class to a prepared statement. Implementors
* should handle possibility of null values. A multi-column type should be written
* to parameters starting from <tt>index</tt>.
*
* @param st a JDBC prepared statement
* @param value the object to write
* @param index statement parameter index
* @param session
* @throws org.hibernate.HibernateException
*
* @throws java.sql.SQLException
*/
@Override
public void nullSafeSet(PreparedStatement st, Object value, int index, SessionImplementor session) throws HibernateException, SQLException {
if (value == null) {
st.setNull(index, Types.OTHER);
return;
}

st.setObject(index, value, Types.OTHER);
}

/**
* Return a deep copy of the persistent state, stopping at entities and at
* collections. It is not necessary to copy immutable objects, or null
* values, in which case it is safe to simply return the argument.
*
* @param value the object to be cloned, which may be null
* @return Object a copy
*/
@Override
public Object deepCopy(Object value) throws HibernateException {

return value;
}

/**
* Are objects of this type mutable?
*
* @return boolean
*/
@Override
public boolean isMutable() {
return true;
}

/**
* Transform the object into its cacheable representation. At the very least this
* method should perform a deep copy if the type is mutable. That may not be enough
* for some implementations, however; for example, associations must be cached as
* identifier values. (optional operation)
*
* @param value the object to be cached
* @return a cachable representation of the object
* @throws org.hibernate.HibernateException
*
*/
@Override
public Serializable disassemble(Object value) throws HibernateException {
return (String)this.deepCopy( value);
}

/**
* Reconstruct an object from the cacheable representation. At the very least this
* method should perform a deep copy if the type is mutable. (optional operation)
*
* @param cached the object to be cached
* @param owner the owner of the cached object
* @return a reconstructed object from the cachable representation
* @throws org.hibernate.HibernateException
*
*/
@Override
public Object assemble(Serializable cached, Object owner) throws HibernateException {
return this.deepCopy( cached);
}

/**
* During merge, replace the existing (target) value in the entity we are merging to
* with a new (original) value from the detached entity we are merging. For immutable
* objects, or null values, it is safe to simply return the first parameter. For
* mutable objects, it is safe to return a copy of the first parameter. For objects
* with component values, it might make sense to recursively replace component values.
*
* @param original the value from the detached entity being merged
* @param target the value in the managed entity
* @return the value to be merged
*/
@Override
public Object replace(Object original, Object target, Object owner) throws HibernateException {
return original;
}
}

上面的类实现Hibernate的UserType接口,这样就可以定义Hibernate端的数据类型,这样将对应数据库中Json字段的JavaBean中的数据字段定义成String类型,这样javaBean中指定字段就能和数据库中json形成映射

例如

1
2
3
<property name="atts" type="com.quangao.hibernate.StringJsonUserType">
<column name="atts" />
</property>

这样session.save(entity);的时候就可以将json字段成功保存

结论

上述俩种方法,第一种虽能实现,但需在数据中频繁转换,本身简单的保存修改变得很麻烦,明显不切符合解耦概念,这是将前端的事情强加给数据库来处理的,而且不符合Hibernate存在的原因,二第二种方法很好的实现了所谓的映射关系,数据库端不需要做任何多余的事情。所以我选择了第二种方案。

当前网速较慢或者你使用的浏览器不支持博客特定功能,请尝试刷新或换用Chrome、Firefox等现代浏览器